package de.lessvoid.nifty.renderer.jogl.render.io;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.imageio.ImageIO;

import com.jogamp.common.nio.Buffers;

/**
 * A utility to load TGAs. Note: NOT THREAD SAFE Fresh cut of code but largely influenced by the TGA
 * loading class provided as part of the Java Monkey Engine (JME). Why not check out what they're
 * doing over at http://www.jmonkeyengine.com. kudos to Mark Powell.
 * 
 * @author Kevin Glass, Julien Gouesse (port to JOGL 2)
 */
public class TGAImageData implements ImageData {
    /** The width of the texture that needs to be generated */
    private int texWidth;

    /** The height of the texture that needs to be generated */
    private int texHeight;

    /** The width of the TGA image */
    private int width;

    /** The height of the TGA image */
    private int height;

    /** The bit depth of the image */
    private short pixelDepth;

    /**
     * Create a new TGA Loader
     */
    public TGAImageData() {
    }

    /**
     * Flip the endian-ness of the short
     * 
     * @param signedShort The short to flip
     * @return The flipped short
     */
    private short flipEndian(short signedShort) {
        int input = signedShort & 0xFFFF;
        return (short) (input << 8 | (input & 0xFF00) >>> 8);
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getDepth()
     */
    public int getDepth() {
        return pixelDepth;
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getWidth()
     */
    public int getWidth() {
        return width;
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getHeight()
     */
    public int getHeight() {
        return height;
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getTexWidth()
     */
    public int getTexWidth() {
        return texWidth;
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getTexHeight()
     */
    public int getTexHeight() {
        return texHeight;
    }

    /**
     * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream)
     */
    public ByteBuffer loadImage(InputStream fis) throws IOException {
        return loadImage(fis, false, null);
    }

    /**
     * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean,
     *      int[])
     */
    public ByteBuffer loadImage(InputStream fis, boolean flipped, int[] transparent)
            throws IOException {
        return loadImage(fis, flipped, true, transparent);
    }

    /**
     * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean,
     *      boolean, int[])
     */
    public ByteBuffer loadImage(InputStream fis, boolean flipped, boolean forceAlpha,
            int[] transparent) throws IOException {
        if (transparent != null) {
            forceAlpha = true;
        }
        byte red = 0;
        byte green = 0;
        byte blue = 0;
        byte alpha = 0;

        BufferedInputStream bis = new BufferedInputStream(fis, 100000);
        DataInputStream dis = new DataInputStream(bis);

        // Read in the Header
        short idLength = (short) dis.read();
        short colorMapType = (short) dis.read();
        short imageType = (short) dis.read();
        short cMapStart = flipEndian(dis.readShort());
        short cMapLength = flipEndian(dis.readShort());
        short cMapDepth = (short) dis.read();
        short xOffset = flipEndian(dis.readShort());
        short yOffset = flipEndian(dis.readShort());

        width = flipEndian(dis.readShort());
        height = flipEndian(dis.readShort());
        pixelDepth = (short) dis.read();
        if (pixelDepth == 32) {
            forceAlpha = false;
        }

        texWidth = get2Fold(width);
        texHeight = get2Fold(height);

        short imageDescriptor = (short) dis.read();
        if ((imageDescriptor & 0x0020) == 0) {
            flipped = !flipped;
        }

        // Skip image ID
        if (idLength > 0) {
            bis.skip(idLength);
        }

        byte[] rawData = null;
        if ((pixelDepth == 32) || (forceAlpha)) {
            pixelDepth = 32;
            rawData = new byte[texWidth * texHeight * 4];
        }
        else if (pixelDepth == 24) {
            rawData = new byte[texWidth * texHeight * 3];
        }
        else {
            throw new RuntimeException("Only 24 and 32 bit TGAs are supported");
        }

        if (pixelDepth == 24) {
            if (flipped) {
                for (int i = height - 1; i >= 0; i--) {
                    for (int j = 0; j < width; j++) {
                        blue = dis.readByte();
                        green = dis.readByte();
                        red = dis.readByte();

                        int ofs = ((j + (i * texWidth)) * 3);
                        rawData[ofs] = red;
                        rawData[ofs + 1] = green;
                        rawData[ofs + 2] = blue;
                    }
                }
            }
            else {
                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        blue = dis.readByte();
                        green = dis.readByte();
                        red = dis.readByte();

                        int ofs = ((j + (i * texWidth)) * 3);
                        rawData[ofs] = red;
                        rawData[ofs + 1] = green;
                        rawData[ofs + 2] = blue;
                    }
                }
            }
        }
        else if (pixelDepth == 32) {
            if (flipped) {
                for (int i = height - 1; i >= 0; i--) {
                    for (int j = 0; j < width; j++) {
                        blue = dis.readByte();
                        green = dis.readByte();
                        red = dis.readByte();
                        if (forceAlpha) {
                            alpha = (byte) 255;
                        }
                        else {
                            alpha = dis.readByte();
                        }

                        int ofs = ((j + (i * texWidth)) * 4);

                        rawData[ofs] = red;
                        rawData[ofs + 1] = green;
                        rawData[ofs + 2] = blue;
                        rawData[ofs + 3] = alpha;

                        if (alpha == 0) {
                            rawData[ofs + 2] = (byte) 0;
                            rawData[ofs + 1] = (byte) 0;
                            rawData[ofs] = (byte) 0;
                        }
                    }
                }
            }
            else {
                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        blue = dis.readByte();
                        green = dis.readByte();
                        red = dis.readByte();
                        if (forceAlpha) {
                            alpha = (byte) 255;
                        }
                        else {
                            alpha = dis.readByte();
                        }

                        int ofs = ((j + (i * texWidth)) * 4);

                        if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
                            rawData[ofs] = red;
                            rawData[ofs + 1] = green;
                            rawData[ofs + 2] = blue;
                            rawData[ofs + 3] = alpha;
                        }
                        else {
                            rawData[ofs] = red;
                            rawData[ofs + 1] = green;
                            rawData[ofs + 2] = blue;
                            rawData[ofs + 3] = alpha;
                        }

                        if (alpha == 0) {
                            rawData[ofs + 2] = 0;
                            rawData[ofs + 1] = 0;
                            rawData[ofs] = 0;
                        }
                    }
                }
            }
        }
        fis.close();

        if (transparent != null) {
            for (int i = 0; i < rawData.length; i += 4) {
                boolean match = true;
                for (int c = 0; c < 3; c++) {
                    if (rawData[i + c] != transparent[c]) {
                        match = false;
                    }
                }

                if (match) {
                    rawData[i + 3] = 0;
                }
            }
        }

        // Get a pointer to the image memory
        ByteBuffer scratch = Buffers.newDirectByteBuffer(rawData.length);
        scratch.put(rawData);

        int perPixel = pixelDepth / 8;
        if (height < texHeight - 1) {
            int topOffset = (texHeight - 1) * (texWidth * perPixel);
            int bottomOffset = (height - 1) * (texWidth * perPixel);
            for (int x = 0; x < texWidth * perPixel; x++) {
                scratch.put(topOffset + x, scratch.get(x));
                scratch.put(bottomOffset + (texWidth * perPixel) + x,
                        scratch.get((texWidth * perPixel) + x));
            }
        }
        if (width < texWidth - 1) {
            for (int y = 0; y < texHeight; y++) {
                for (int i = 0; i < perPixel; i++) {
                    scratch.put(((y + 1) * (texWidth * perPixel)) - perPixel + i,
                            scratch.get(y * (texWidth * perPixel) + i));
                    scratch.put((y * (texWidth * perPixel)) + (width * perPixel) + i,
                            scratch.get((y * (texWidth * perPixel)) + ((width - 1) * perPixel) + i));
                }
            }
        }

        scratch.flip();

        return scratch;
    }

    public BufferedImage loadMouseCursorImage(InputStream fis) throws IOException {
      return ImageIO.read(fis);
    }
    
    public ByteBuffer loadImageDirect(InputStream fis) throws IOException {
      return loadImage(fis, false, true, null);
    }

    /**
     * Get the closest greater power of 2 to the fold number
     * 
     * @param fold The target number
     * @return The power of 2
     */
    private int get2Fold(int fold) {
        int ret = 2;
        while (ret < fold) {
            ret *= 2;
        }
        return ret;
    }

    /**
     * @see org.newdawn.slick.opengl.ImageData#getImageBufferData()
     */
    public ByteBuffer getImageBufferData() {
        throw new RuntimeException("TGAImageData doesn't store it's image.");
    }

    /**
     * @see org.newdawn.slick.opengl.LoadableImageData#configureEdging(boolean)
     */
    public void configureEdging(boolean edging) {
    }
}
